// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: gekim

#include <memory>
#include <string>
#include <regex>

#include <boost/algorithm/string.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>

#include "coin2/base/log.h"
#include "coin2/base/request/session_pool.h"
#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/base/feed/ws/kucoin_connection.h"
#include "coin2/exchange/base/feed/ws/rest_connection.h"
#include "coin2/exchange/util/json_util.h"

namespace request = coin2::base::request;

namespace coin2::exchange::base::feed {
namespace impl {

KucoinConnection::KucoinConnection(
    const WsConnectionConfig* config, std::function<void()> fail_callback,
    executor::LiveAsyncExecutor* exec, RequestGenerator* request_generator,
    std::shared_ptr<LatencyExtractor> latency_extractor, bool enable_telemetry)
    : WsConnection(*config, fail_callback, exec, request_generator,
                   latency_extractor, enable_telemetry) {
  uuid_ = boost::uuids::random_generator()();
  ping_interval_ns_ = config->mea_config->ping_interval_ns;
}

KucoinConnection::~KucoinConnection() {}

void KucoinConnection::Run(boost::asio::yield_context yield) {
  try {
    boost::system::error_code ec;
    boost::asio::steady_timer timer(*exec_->RawIoc());

    std::unique_ptr<RestConnection> connection = std::make_unique<RestConnection>(
        exec_->RawIoc(),
        "https://" + config_.url.host,
        std::stoi(config_.url.port),
        config_.url.bind_ip,
        config_.url.proxy);
    connection->Start(yield[ec]);

    rapidjson::Document document;
    std::string url_host, url_path;
    if (!ec) {
      std::string response;
      bool status = connection->Post(config_.url.path, &response, yield[ec]);
      if (!status) {
        return;
      }

      document.Parse<rapidjson::kParseNumbersAsStringsFlag>(response.data());
      if (document.HasParseError()) {
        return;
      }

      auto& doc_data = document["data"];
      if (!doc_data["instanceServers"].IsArray()) {
        return;
      }
      const auto& doc_instance_servers = doc_data["instanceServers"].GetArray();
      if (doc_instance_servers.Size() == 0) {
        return;
      }

      std::string url_endpoint =
          doc_instance_servers[0]["endpoint"].GetString();
      ping_interval_ns_ =
          std::stoll(doc_instance_servers[0]["pingInterval"].GetString()) *
          1000000LL;
      std::size_t pos = url_endpoint.find("wss://");
      if (pos != std::string::npos) {
        std::size_t pos_url_begin = pos + sizeof("wss://") - 1;
        std::size_t pos_url_end = url_endpoint.find("/", pos_url_begin);
        url_host =
            url_endpoint.substr(pos_url_begin, pos_url_end - pos_url_begin);
        url_path = fmt::format(
            "{}?token={}&connectId={}", url_endpoint.substr(pos_url_end),
            doc_data["token"].GetString(), boost::uuids::to_string(uuid_));
      }
    } else {
      LOG(INFO) << ec;
    }

    timer.expires_from_now(std::chrono::seconds(1));
    timer.async_wait(yield[ec]);

    LOG(INFO) << url_host;
    WsConnection::Run(yield, url_host, config_.url.port, url_path);
  } catch (const std::exception& e) {
    std::cerr << e.what() << '\n';
  }
}

void KucoinConnection::SchedulePingPong() {
  // Schedule sending periodic ping if needed.
  if (!config_.mea_config->periodic_ping_msg.empty()) {
    std::string ping_msg = config_.mea_config->periodic_ping_msg;
    boost::replace_all(ping_msg, "{uuid}", boost::uuids::to_string(uuid_));
    DLOG(INFO) << config_.topic << "_" << config_.url.name << ", ping," << ping_msg;
    scheduler_->Start();
    std::weak_ptr<WsConnection> self(shared_from_this());
    scheduler_->AddPeriodicTask(
        [self, ping_msg] {
          if (auto s = self.lock()) {
            s->Send(ping_msg);
          } else {
            LOG(ERROR) << "WsConnection handler is already destroyed";
          }
        },
        ping_interval_ns_,
        "customized ping");
  }
}

}  // namespace impl
}  // namespace coin2::exchange::base::feed
